use chrono::Utc;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use reqwest;
use serde::de::DeserializeOwned;
use std::error::Error;
use crate::models;

fn random_alpha_numeric(num: u32) -> String {
    let rand_string: String = thread_rng()
        .sample_iter(&Alphanumeric)
        .take(num as usize)
        .map(char::from)
        .collect();
    rand_string
}

fn random_once_str() -> String {
    random_alpha_numeric(10)
}

fn unix_time() -> i64 {
    let current_time = Utc::now().timestamp();
    current_time
}

fn build_to_sign_content(
    method: &str,
    path: &str,
    p_timestamp: i64,
    once_str: &str,
    req_body_str: &str,
) -> String {
    format!(
        "{}\n{}\n{}\n{}\n{}\n",
        method, path, p_timestamp, once_str, req_body_str
    )
}

fn build_signature(p_content: &str, p_private_key: &str) -> Result<String, Box<dyn Error>> {
    use crate::rsa_helper;
    let signature = rsa_helper::rsa_sign(
        p_content.to_string(),
        p_private_key.to_string(),
        rsa_helper::HashType::Sha256,
    )?;
    Ok(signature)
}

fn build_authorization_content(
    mch_id: &str,
    serial_num: &str,
    p_timestamp: i64,
    once_str: &str,
    signature: &str,
) -> String {
    format!("WECHATPAY2-SHA256-RSA2048 mchid=\"{}\",nonce_str=\"{}\",timestamp=\"{}\",signature=\"{}\",serial_no=\"{}\"",
    mch_id,
    serial_num,
    p_timestamp,
    once_str,
    signature)
}

pub struct WxPayment {
    app_id: String,
    mch_id: String,
    serial_num: String,
    private_key: String,
    api_v3_private_key: String,
}

impl WxPayment {
    fn build_authorization(
        &self,
        http_method: &str,
        req_path: &str,
        body_str: &str,
    ) -> Result<String, Box<dyn Error>> {
        let timestamp = unix_time();
        let once_string = random_once_str();
        let sign_content = build_to_sign_content(
            http_method,
            req_path,
            timestamp,
            once_string.as_str(),
            body_str,
        );
        let signature = build_signature(sign_content.as_str(), self.private_key.as_str())?;
        let authorization = build_authorization_content(
            self.mch_id.as_str(),
            self.serial_num.as_str(),
            timestamp,
            once_string.as_str(),
            signature.as_str(),
        );
        Ok(authorization)
    }

    pub fn get_app_id(&self) -> String {
        self.app_id.clone().to_string()
    }

    pub fn get_mch_id(&self) -> String {
        self.mch_id.clone().to_string()
    }

    async fn request_server<T: DeserializeOwned>(
        &self,
        http_method: &str,
        req_path: &str,
        body_str: &str,
    ) -> Result<T, Box<dyn Error>> {
        let authorization = self.build_authorization(http_method, req_path, body_str)?;
        let req_url = format!("https://api.mch.weixin.qq.com{}", req_path);

        let request = match http_method {
            "POST" => reqwest::Client::new().post(req_url),
            _ => reqwest::Client::new().get(req_url),
        };

        let resp = request.header("Accept", "application/json")
        .header("Content-Type", "application/json;charset=utf-8")
        .header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36")
        .header("Authorization", authorization)
        .send()
        .await?;
        let resp_status = resp.status().as_u16();
        if resp_status == 200 {
            let result = resp.json::<T>().await?; 
            Ok(result)
        }else if resp_status >= 400  { 
            let err_resp:models::ErrorResp = resp.json::<models::ErrorResp>().await?; 
            let msg = format!("返回数据码为:{},错误信息为:{}",resp_status,err_resp.message.unwrap_or("错误信息".to_owned()));
            return Err(Box::<dyn Error + Send + Sync>::from(msg));
        }else {
            let msg = format!("返回数据码为:{}",resp_status);
            return Err(Box::<dyn Error + Send + Sync>::from(msg));
        }
         
        
    }

    pub async fn request_order_payment(&self,req_order:&models::RequestOrder) -> Result<models::RequestOrderResp,Box<dyn Error>>{
        let body_str = serde_json::to_string(req_order)?;
        let http_method = "Post";
        let req_path = "/v3/pay/transactions/jsapi";
        let resp = self.request_server::<models::RequestOrderResp>(http_method, req_path, body_str.as_str()).await?;
        Ok(resp)
    }


    pub async fn load_order_payment(&self,out_trade_no:&str) -> Result<models::PaymentOrder,Box<dyn Error>>{
        let body_str = "";
        let http_method = "GET";
        let req_path = format!("/v3/pay/transactions/out-trade-no/{}?mchid={}",out_trade_no,self.mch_id);
        let resp = self.request_server::<models::PaymentOrder>(http_method, req_path.as_str(), body_str).await?;
        Ok(resp)
    }

    pub async fn close_order_payment(&self,out_trade_no:&str) -> Result<bool,Box<dyn Error>>{
        let body_str = format!("{{\"mchid\": \"{}\"}}",self.mch_id);
        let http_method = "POST";
        let req_path = format!("/v3/pay/transactions/out-trade-no/{}/close",out_trade_no);
        let authorization = self.build_authorization(http_method, req_path.as_str(), body_str.as_str())?;
        let req_url = format!("https://api.mch.weixin.qq.com{}", req_path);  
        let resp = reqwest::Client::new().post(req_url).header("Accept", "application/json")
        .header("Content-Type", "application/json;charset=utf-8")
        .header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36")
        .header("Authorization", authorization)
        .send()
        .await?;
        
        Ok(resp.status().as_u16()==204)
    }


    pub async fn request_refund_order_payment(&self,req_refund:&models::RequestRefund) -> Result<models::RefundResp,Box<dyn Error>>{
        let body_str = serde_json::to_string(req_refund)?;
        let http_method = "Post";
        let req_path = "/v3/refund/domestic/refunds";
        let resp = self.request_server::<models::RefundResp>(http_method, req_path, body_str.as_str()).await?;
        Ok(resp)
    }

    pub async fn load_refund_order(&self,out_refund_no:&str) -> Result<models::RefundResp,Box<dyn Error>>{
        let body_str = "";
        let http_method = "GET";
        let req_path = format!("v3/refund/domestic/refunds/{}",out_refund_no);
        let resp = self.request_server::<models::RefundResp>(http_method, req_path.as_str(), body_str).await?;
        Ok(resp)
    }

    
}
